package cn.schoolwow.sdk.aliyundrive;

import cn.schoolwow.quickhttp.QuickHttp;
import cn.schoolwow.quickhttp.domain.RequestMeta;
import cn.schoolwow.quickhttp.listener.SimpleQuickHttpClientListener;
import cn.schoolwow.quickhttp.request.Request;
import cn.schoolwow.quickhttp.response.Response;
import cn.schoolwow.sdk.aliyundrive.domain.*;
import cn.schoolwow.sdk.util.DigestUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.List;
import java.util.Scanner;

public class ALiYunDriveAPIImpl implements ALiYunDriveAPI{
    private Logger logger = LoggerFactory.getLogger(ALiYunDriveAPIImpl.class);
    
    /**认证token*/
    private String token;
    
    /**用户信息*/
    private ALiYunDriveUser aLiYunDriveUser;

    /**
     * 阿里云盘Cookie
     * @param cookieString aliyundrive.com域名下的所有Cookie信息
     * */
    public ALiYunDriveAPIImpl(String token) {
        this.token = token;
        QuickHttp.clientConfig().quickHttpClientListener(new SimpleQuickHttpClientListener() {
            @Override
            public void beforeExecute(Request request) throws IOException{
                RequestMeta requestMeta = request.requestMeta();
                if(requestMeta.url.getHost().contains("aliyundrive.com")){
                    request.method(Request.Method.POST)
                            .setHeader("Authorization","Bearer "+ token)
                            .ignoreHttpErrors(true);
                    if(null==requestMeta.contentType){
                        request.contentType(Request.ContentType.APPLICATION_JSON);
                    }
                }
            }

            @Override
            public void executeSuccess(Request request, Response response) throws IOException{
                JSONObject result = response.bodyAsJSONObject();
                if(null!=result&&result.containsKey("code")){
                    throw new IOException("请求失败!代码:"+result.getString("code")+",消息:"+result.getString("message"));
                }
            }
        });
        //获取driveId
        try {
            aLiYunDriveUser = getUser();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setToken(String token) throws IOException {
        this.token = token;
        try {
            aLiYunDriveUser = getUser();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ALiYunDriveUser getUser() throws IOException {
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/user/get")
                .requestBody("{}")
                .execute();
        ALiYunDriveUser aLiYunDriveUser = response.bodyAsJSONObject().toJavaObject(ALiYunDriveUser.class);
        return aLiYunDriveUser;
    }

    @Override
    public ALiYunDriveSBox getSbox() throws IOException {
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/sbox/get")
                .requestBody("{}")
                .execute();
        ALiYunDriveSBox aLiYunDriveSBox = response.bodyAsJSONObject().toJavaObject(ALiYunDriveSBox.class);
        return aLiYunDriveSBox;
    }

    @Override
    public List<ALiYunDriveFile> search(String keyword) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"limit\":100,\"query\":\"name match \\\""+keyword+"\\\"\",\"image_thumbnail_process\":\"image/resize,w_200/format,jpeg\",\"image_url_process\":\"image/resize,w_1920/format,jpeg\",\"video_thumbnail_process\":\"video/snapshot,t_0,f_jpg,ar_auto,w_300\",\"order_by\":\"updated_at DESC\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v3/file/search")
                .requestBody(data)
                .execute();
        JSONArray items = response.bodyAsJSONObject().getJSONArray("items");
        return items.toJavaList(ALiYunDriveFile.class);
    }

    @Override
    public List<ALiYunDriveFile> list(ALiYunDriveDirectory parentALiYunDriveFile) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"parent_file_id\":\""+(null==parentALiYunDriveFile?"root":parentALiYunDriveFile.fileId)+"\",\"limit\":100,\"all\":false,\"url_expire_sec\":1600,\"image_thumbnail_process\":\"image/resize,w_400/format,jpeg\",\"image_url_process\":\"image/resize,w_1920/format,jpeg\",\"video_thumbnail_process\":\"video/snapshot,t_0,f_jpg,ar_auto,w_300\",\"fields\":\"*\",\"order_by\":\"updated_at\",\"order_direction\":\"DESC\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v3/file/list")
                .requestBody(data)
                .execute();
        JSONArray items = response.bodyAsJSONObject().getJSONArray("items");
        return items.toJavaList(ALiYunDriveFile.class);
    }

    @Override
    public ALiYunDriveDirectory createWithFolders(ALiYunDriveDirectory parentALiYunDriveFile, String name) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"parent_file_id\":\""+(null==parentALiYunDriveFile?"root":parentALiYunDriveFile.fileId)+"\",\"name\":\""+name+"\",\"check_name_mode\":\"refuse\",\"type\":\"folder\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
                .requestBody(data)
                .execute();
        ALiYunDriveDirectory aLiYunDriveFile = response.bodyAsJSONObject().toJavaObject(ALiYunDriveDirectory.class);
        return aLiYunDriveFile;
    }

    @Override
    public ALiYunDriveFile uploadFile(ALiYunDriveDirectory parentALiYunDriveFile, String filePath) throws IOException {
        Path path = Paths.get(filePath);
        if(Files.notExists(path)){
            throw new IOException("文件不存在!路径:"+filePath);
        }
        //计算sha1
        byte[] fileBytes = Files.readAllBytes(path);
        String sha1 = DigestUtil.SHA1Hex(fileBytes);

        //计算proofCode
        String proofCode = null;
        try {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("javascript");
            URL url = ClassLoader.getSystemResource("js/proof.js");
            Scanner scanner = new Scanner(url.openStream());
            StringBuilder builder = new StringBuilder();
            while(scanner.hasNextLine()){
                builder.append(scanner.nextLine()+"\n");
            }
            scanner.close();
            engine.eval(builder.toString());
            String r = ((Invocable)engine).invokeFunction("v",token).toString().substring(0,16);
            int left = (int) (Long.parseLong(r,16)%fileBytes.length);
            int right = Math.min(left+8,fileBytes.length);
            byte[] bytes = new byte[right-left];
            System.arraycopy(fileBytes,left,bytes,0,bytes.length);
            proofCode = Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"part_info_list\":[{\"part_number\":1}],\"parent_file_id\":\""+(null==parentALiYunDriveFile?"root":parentALiYunDriveFile.fileId)+"\",\"name\":\""+path.getFileName()+"\",\"type\":\"file\",\"check_name_mode\":\"auto_rename\",\"size\":"+Files.size(path)+",\"content_hash\":\""+sha1+"\",\"content_hash_name\":\"sha1\",\"proof_code\":\""+proofCode+"\",\"proof_version\":\"v1\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
                .requestBody(data)
                .execute();
        //判断是否为秒传
        JSONObject result = response.bodyAsJSONObject();
        if(!result.getBoolean("rapid_upload")){
            //正常上传
            JSONArray array = result.getJSONArray("part_info_list");
            for(int i=0;i<array.size();i++){
                JSONObject o = array.getJSONObject(i);
                response = QuickHttp.connect(o.getString("upload_url"))
                        .method(Request.Method.PUT)
                        .contentType(o.getString("content_type"))
                        .requestBody(path)
                        .execute();
            }
        }
        //上传完成
        data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"upload_id\":\""+result.getString("upload_id")+"\",\"file_id\":\""+result.getString("file_id")+"\"}";
        response = QuickHttp.connect("https://api.aliyundrive.com/v2/file/complete")
                .requestBody(data)
                .execute();
        ALiYunDriveFile aLiYunDriveFile = response.bodyAsJSONObject().toJavaObject(ALiYunDriveFile.class);
        return aLiYunDriveFile;
    }

    @Override
    public ALiYunDriveFile update(ALiYunDriveFile aLiYunDriveFile, String name) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\",\"name\":\""+name+"\",\"check_name_mode\":\"refuse\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v3/file/update")
                .requestBody(data)
                .execute();
        aLiYunDriveFile = response.bodyAsJSONObject().toJavaObject(ALiYunDriveFile.class);
        return aLiYunDriveFile;
    }

    @Override
    public void move(ALiYunDriveFile sourceALiYunDriveFile, ALiYunDriveDirectory targetALiYunDriveDirectory) throws IOException {
        batch("[{\"body\":{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+sourceALiYunDriveFile.fileId+"\",\"to_drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"to_parent_file_id\":\""+(null==targetALiYunDriveDirectory?"root":targetALiYunDriveDirectory.fileId)+"\"},\"headers\":{\"Content-Type\":\"application/json\"},\"id\":\""+sourceALiYunDriveFile.fileId+"\",\"method\":\"POST\",\"url\":\"/file/move\"}]");
    }

    @Override
    public List<ALiYunDriveFile> listRecycle() throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"limit\":100,\"image_thumbnail_process\":\"image/resize,w_400/format,jpeg\",\"video_thumbnail_process\":\"video/snapshot,t_0,f_jpg,ar_auto,w_400\",\"order_by\":\"name\",\"order_direction\":\"DESC\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/recyclebin/list")
                .requestBody(data)
                .execute();
        JSONArray items = response.bodyAsJSONObject().getJSONArray("items");
        return items.toJavaList(ALiYunDriveFile.class);
    }

    @Override
    public String trash(ALiYunDriveFile aLiYunDriveFile) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/recyclebin/trash")
                .requestBody(data)
                .execute();
        JSONObject result = response.bodyAsJSONObject();
        return null==result?null:result.getString("async_task_id");
    }

    @Override
    public String restore(ALiYunDriveFile aLiYunDriveFile) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/recyclebin/restore")
                .requestBody(data)
                .execute();
        JSONObject result = response.bodyAsJSONObject();
        return null==result?null:result.getString("async_task_id");
    }

    @Override
    public void delete(ALiYunDriveFile aLiYunDriveFile) throws IOException {
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v3/file/delete")
                .requestBody(data)
                .execute();
    }

    @Override
    public void batchDelete(ALiYunDriveFile... aLiYunDriveFiles) throws IOException {
        if(null==aLiYunDriveFiles||aLiYunDriveFiles.length==0){
            throw new IllegalArgumentException("分享链接列表不能为空!");
        }
        StringBuilder builder = new StringBuilder("[");
        for(ALiYunDriveFile aLiYunDriveFile:aLiYunDriveFiles){
            builder.append("{\"body\":{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\"},\"headers\":{\"Content-Type\":\"application/json\"},\"id\":\""+aLiYunDriveFile.fileId+"\",\"method\":\"POST\",\"url\":\"/recyclebin/trash\"},");
        }
        builder.deleteCharAt(builder.length()-1);
        builder.append("]");
        batch(builder.toString());
    }

    @Override
    public List<ALiYunDriveShare> listShare() throws IOException {
        String data = "{\"creator\":\""+aLiYunDriveUser.userId+"\",\"include_canceled\":false,\"order_by\":\"created_at\",\"order_direction\":\"DESC\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v3/share_link/list")
                .contentType("application/json")
                .requestBody(data)
                .execute();
        JSONArray items = response.bodyAsJSONObject().getJSONArray("items");
        return items.toJavaList(ALiYunDriveShare.class);
    }

    @Override
    public ALiYunDriveShare create(LocalDateTime expireTime, ALiYunDriveFile... aLiYunDriveFiles) throws IOException {
        if(null==aLiYunDriveFiles||aLiYunDriveFiles.length==0){
            throw new IllegalArgumentException("分享链接不能为空!");
        }
        StringBuilder builder = new StringBuilder();
        for(ALiYunDriveFile aLiYunDriveFile:aLiYunDriveFiles){
            builder.append("\"" + aLiYunDriveFile.fileId + "\",");
        }
        builder.deleteCharAt(builder.length()-1);
        String data = "{\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id_list\":["+builder.toString()+"],\"expiration\":\""+(null==expireTime?"":expireTime.format(DateTimeFormatter.ISO_DATE_TIME)+"Z")+"\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v2/share_link/create")
                .contentType("application/json")
                .requestBody(data)
                .execute();
        ALiYunDriveShare aLiYunDriveShare = response.bodyAsJSONObject().toJavaObject(ALiYunDriveShare.class);
        return aLiYunDriveShare;
    }

    @Override
    public ALiYunDriveShare update(LocalDateTime expireTime, ALiYunDriveShare aLiYunDriveShare) throws IOException {
        String data = "{\"share_id\":\""+aLiYunDriveShare.shareId+"\",\"expiration\":\""+(null==expireTime?"":expireTime.format(DateTimeFormatter.ISO_DATE_TIME)+"Z")+"\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/share_link/update")
                .contentType("application/json")
                .requestBody(data)
                .execute();
        aLiYunDriveShare = response.bodyAsJSONObject().toJavaObject(ALiYunDriveShare.class);
        return aLiYunDriveShare;
    }

    @Override
    public void cancelShare(ALiYunDriveShare... aLiYunDriveFileShares) throws IOException {
        if(null==aLiYunDriveFileShares||aLiYunDriveFileShares.length==0){
            throw new IllegalArgumentException("分享链接列表不能为空!");
        }
        StringBuilder builder = new StringBuilder("[");
        for(ALiYunDriveShare aLiYunDriveShare:aLiYunDriveFileShares){
            builder.append("{\"body\":{\"share_id\":\""+aLiYunDriveShare.shareId+"\"},\"headers\":{\"Content-Type\":\"application/json\"},\"id\":\""+aLiYunDriveShare.shareId+"\",\"method\":\"POST\",\"url\":\"/share_link/cancel\"},");
        }
        builder.deleteCharAt(builder.length()-1);
        builder.append("]");
        batch(builder.toString());
    }

    @Override
    public List<ALiYunDriveFile> listStarred() throws IOException {
        String data = "{\"custom_index_key\":\"starred_yes\",\"parent_file_id\":\"root\",\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"fields\":\"*\",\"image_thumbnail_process\":\"image/resize,w_200/format,jpeg\",\"image_url_process\":\"image/resize,w_1920/format,jpeg\",\"video_thumbnail_process\":\"video/snapshot,t_0,f_jpg,ar_auto,w_300\",\"order_by\":\"name\",\"order_direction\":\"DESC\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/file/list_by_custom_index_key")
                .requestBody(data)
                .execute();
        JSONArray items = response.bodyAsJSONObject().getJSONArray("items");
        return items.toJavaList(ALiYunDriveFile.class);
    }

    @Override
    public ALiYunDriveFile starred(ALiYunDriveFile aLiYunDriveFile) throws IOException {
        String data = "{\"custom_index_key\":\"starred_yes\",\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\",\"starred\":true}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/file/update")
                .requestBody(data)
                .execute();
        aLiYunDriveFile = response.bodyAsJSONObject().toJavaObject(ALiYunDriveFile.class);
        return aLiYunDriveFile;
    }

    @Override
    public ALiYunDriveFile cancelStarred(ALiYunDriveFile aLiYunDriveFile) throws IOException {
        String data = "{\"custom_index_key\":\"starred_yes\",\"drive_id\":\""+aLiYunDriveUser.defaultDriveId+"\",\"file_id\":\""+aLiYunDriveFile.fileId+"\",\"starred\":false}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/v2/file/update")
                .requestBody(data)
                .execute();
        aLiYunDriveFile = response.bodyAsJSONObject().toJavaObject(ALiYunDriveFile.class);
        return aLiYunDriveFile;
    }

    /**批量操作*/
    private void batch(String requests) throws IOException {
        String data = "{\"requests\":"+requests+",\"resource\":\"file\"}";
        Response response = QuickHttp.connect("https://api.aliyundrive.com/adrive/v2/batch")
                .contentType("application/json")
                .requestBody(data)
                .execute();
        JSONObject result = response.bodyAsJSONObject();
        logger.info("[批量操作]{}", result.getString("responses"));
    }
}